package com.foreveross.proxyip.core.zk;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import net.sf.json.JSONObject;

import org.apache.log4j.Logger;
import org.apache.zookeeper.KeeperException;

import com.foreveross.proxyip.core.redis.RedisExecutor;
import com.foreveross.proxyip.core.redis.RedisManager;
import com.foreveross.proxyip.util.SerializeUtil;


/**
 * zookeeper计划
 * @author lyq
 *
 */
public class ZkPlan {
	 protected static final Logger log =  Logger.getLogger(ZkPlan.class);
	 
	 protected RedisManager redisManager = RedisManager.getInstance();
	 protected Long appTaskMaxNum = null;//1：平均；2：最大；
	 Long taskNum = null;
	 Integer appNum = null;
	 
	 
	 ZkAppCenter center = null;

	private String taskCode = null;
	
	private String execClassName = null;
	
	private boolean isDoload = false;
	
	private List<Map<String, String>> plans = new ArrayList<Map<String,String>>();
	
	/**
	 * 
	 * 每个应用最大执行任务数
	 * @param taskCode 确定为那个池去执行该任务
	 * @param execClassName 执行类
	 * @param taskNum 任务总数量
	 * @param appTaskMaxNum  每个app执行任务的最大数量
	 * @param center 该应用注册中心
	 * @param isDoload 是否下载 【true：下载】,一般下载就是检入线程
	 * @throws Exception
	 */
	 public ZkPlan(String taskCode,String execClassName,Long taskNum,Long appTaskMaxNum,ZkAppCenter center,boolean isDoload) throws Exception{
		 this.taskCode = taskCode;
		 this.taskNum = taskNum;
		 this.appNum = center.getAppsSize();
		 this.center = center;
		 this.appTaskMaxNum = appTaskMaxNum;
		 this.execClassName = execClassName;
		 this.isDoload = isDoload;
		 this.plan();
	 }
	 
	 // 检入
	 public ZkPlan(String taskCode,String execClassName,Long taskNum,Long appTaskMaxNum,ZkAppCenter center) throws Exception{
		 this(taskCode, execClassName, taskNum, appTaskMaxNum, center, true);
	 }
	 
	// 检出
	public ZkPlan(String taskCode,String execClassName,Long taskNum,ZkAppCenter center) throws Exception{
		 this(taskCode, execClassName, taskNum, null, center, false);
	}
	 
	
	/**
	 * 任务发起人把任务分成几份放入 redis队列
	 * @param obj
	 * @throws Exception
	 */
	private void plan() throws Exception {
		List<Long> tasks = null;
		
		// 暂时全部采用平均任务
		/*if(appTaskMaxNum == null){
			tasks = this.planAvg();
		}else{
			tasks = this.planMaxNum(appTaskMaxNum);
		}*/
		
		tasks = this.planAvg();
		final List<Long[]> finalTasks = this.planFinal(tasks);
		List<String> apps = center.getApps();
		Map<String, String> plan = null;
		
		// 分配每个应用的任务
		for (int i = 0; i < finalTasks.size(); i++) {
			final String key = getPlanKey(taskCode,execClassName);
			final byte[] value = SerializeUtil.serialize(finalTasks.get(i));
			int index = i % apps.size();
			
			// 如果己循环一轮开始重头再次分配，则一组计划分配完成，添加之后new新的计划
			if (index == 0) {
				if (plan != null) {
					plans.add(plan);
				}
				
				plan = new HashMap<String, String>();
			}
			
			plan.put(apps.get(index) + "_" + key , key);
			
			new RedisExecutor<Boolean>() {
				@Override
				public Boolean run() throws Exception {
					this.jedis.setex(key.getBytes(), 60, value);
					return null;
				}
			}.exec();
		}
		
		if (plan != null) {
			plans.add(plan);
		}
	}

	
	
	/**
	 * 分配方式：当每个应用执行maxNum个任务，如果count任务总是超过maxNum个，交给下个应用；
	 * 该模式下，如果应用不够，等待 所有应用执行完后再执行剩余任务
	 * @param count
	 * @param maxNum
	 * @throws Exception 
	 * @throws KeeperException 
	 */
	@SuppressWarnings("unused")
	private List<Long> planMaxNum(Long maxNum) throws Exception{
		List <Long> taskNums = new ArrayList<Long>();
		Long remain = this.taskNum;
		
		while(true){
			for(Integer i= 0; i < appNum; i++){
				Long task = 0L;
				if(maxNum >= this.taskNum){
					task = remain;
					remain = 0L;
					taskNums.add(task);
					break;
				}else{
					task = maxNum;
					remain -=  maxNum;
				}
				
				taskNums.add(task);
			}
			
			if(remain==0){
				break;
			}
		}

		return taskNums;
	}
	
	/**
	 * 平均任务
	 * @param count
	 * @return
	 * @throws Exception
	 */
	private List<Long> planAvg() throws Exception{
		List <Long> taskNums = new ArrayList<Long>();
		
		// 如果任务数还小于应用数，则一个应用直接完成
		if (taskNum < appNum) {
			taskNums.add(taskNum);
			
			return taskNums;
		}
		
		Long remain = taskNum % appNum;
		Long avg = taskNum / appNum ;
		
		for(Integer i = 0 ; i< appNum; i++){
			taskNums.add(avg);
		}
		
		// 如果有余，则每依次加入每个应用数中
		if(remain != 0){
			for(Integer j = 0; j< remain; j++){
				taskNums.set(j, taskNums.get(j)+1);
			}
		}
		
		return taskNums;
	}
	
	/**
	 * 等待分布式任务执行完毕 颁布源ip下载任务。
	 * @param execClassName
	 * @param params
	 * @throws Exception
	 */
	public void executeWait() throws Exception{
		Map<Object, Object> params = new HashMap<Object, Object>();
		String taskParams =  taskCode + "_" + execClassName;
		params.put("taskParams", taskParams);
		
		for (Map<String, String> plan : plans) {
			params.put("plans", plan);
			this.center.executeWait(JSONObject.fromObject(params).toString(), taskParams);
		}
	}
	
	/**
	 * 执行任务 颁布检入检出任务。
	 * @param execClassName
	 * @param params
	 * @throws Exception
	 */
	public void execute() throws Exception{
		Map<Object, Object> params = new HashMap<Object, Object>();
		String taskParams =  taskCode + "_" + execClassName;
		params.put("taskParams", taskParams);
		
		for (Map<String, String> plan : plans) {
			params.put("plans", plan);
			this.center.executeWait(JSONObject.fromObject(params).toString(), taskParams);
		}
	}
	
	private List <Long []> planFinal(List<Long> taskNums) {
		List <Long []> tasks = new ArrayList<Long []>();
		Long begin =0L ,end = 0L;
		
		for(Long task : taskNums){
			// 如果为检入，因为是队列所以只要知道要检测多少ip的数量即
			if(this.isDoload){
				tasks.add(new Long[] {task});
			
			// 检出因为是集合，所以要启始段才行
			}else{
				end += task;
				tasks.add(new Long[] {begin, end});
				begin = end + 1;
			}
		}
		
		return tasks;
	}
	
	public static String getPlanKey(String taskCode,String execClassName) {
		return taskCode+"_"+execClassName+ "_" + UUID.randomUUID().toString() + "_plan";
	}
	
	public static void main(String[] args) {
		for (int i = 0; i < 7; i++) {
			int index = i% 4;
			System.out.println(index);
		}
	}
}
